package com.slowfly.rule.engine.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.slowfly.rule.engine.bean.CacheBean;
import com.slowfly.rule.engine.component.RedisComponent;
import com.slowfly.rule.engine.constants.IRiskConstants;
import com.slowfly.rule.engine.entity.Decision;
import com.slowfly.rule.engine.entity.Strategy;
import com.slowfly.rule.engine.enums.NodeTypeEnum;
import com.slowfly.rule.engine.enums.OptEnum;
import com.slowfly.rule.engine.mapper.DecisionMapper;
import com.slowfly.rule.engine.model.Flow;
import com.slowfly.rule.engine.param.DecisionParam;
import com.slowfly.rule.engine.result.DecisionResult;
import com.slowfly.rule.engine.result.PageResult;
import com.slowfly.rule.engine.service.IDecisionService;
import com.slowfly.rule.engine.service.IStrategyService;
import com.google.common.collect.Maps;
import com.slowfly.rule.engine.util.PageResultConvert;
import com.slowfly.rule.engine.util.TextUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author shalongfei
 * @since 2024-03-06
 */
@Slf4j
@Service
public class DecisionServiceImpl extends ServiceImpl<DecisionMapper, Decision> implements IDecisionService {

    @Autowired
    private IStrategyService strategyService;
    @Autowired
    private RedisComponent redisComponent;

    private Map<Integer, Decision> decisionCache;

    public void setCache() {
        Map<Integer, Decision> decisionCache = Maps.newConcurrentMap();
        for (Decision decision : list()) {
            decisionCache.put(decision.getId(), decision);
        }
        this.decisionCache = decisionCache;
    }

    public void editCache(int type, int id) {
        if (type == OptEnum.ADD.getCode() || type == OptEnum.EDIT.getCode()) {
            decisionCache.put(id, getById(id));
        } else if (type == OptEnum.DELETE.getCode()) {
            decisionCache.remove(id);
        }
    }

    public Decision get(Integer id) {
        return decisionCache.get(id);
    }

    public List<DecisionResult> listDecision() {
        return BeanUtil.copyToList(list(), DecisionResult.class);
    }

    public PageResult<DecisionResult> pageDecision(DecisionParam param) {
        LambdaQueryWrapper<Decision> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Objects.nonNull(param.getId()), Decision::getId, param.getId());
        queryWrapper.like(StringUtils.isNotBlank(param.getName()), Decision::getName, param.getName());
        queryWrapper.eq(Objects.nonNull(param.getResultType()), Decision::getResultType, param.getResultType());
        queryWrapper.orderByDesc(Decision::getUpdateTime);
        Page<Decision> page = page(new Page<>(param.getPageNo(), param.getPageSize()), queryWrapper);
        return PageResultConvert.convert(page, DecisionResult.class);
    }

    public boolean addDecision(DecisionParam param) {
        Decision decision = new Decision();
        BeanUtils.copyProperties(param, decision);
        boolean saved = save(decision);
        redisComponent.publish(IRiskConstants.DECISION_CHANEL, new CacheBean(OptEnum.ADD.getCode(), decision.getId()));
        return saved;
    }

    public boolean editDecision(DecisionParam param) {
        Decision decision = new Decision();
        BeanUtils.copyProperties(param, decision);
        boolean updated = updateById(decision);
        redisComponent.publish(IRiskConstants.DECISION_CHANEL, new CacheBean(OptEnum.EDIT.getCode(), param.getId()));
        return updated;
    }

    public boolean deleteDecision(DecisionParam param) {
        List<Strategy> strategies = strategyService.list();
        for (Strategy strategy : strategies) {
            checkFlows(strategy.getId(), param.getId(), strategy.getFlows());
        }
        boolean removed = removeById(param.getId());
        redisComponent.publish(IRiskConstants.DECISION_CHANEL, new CacheBean(OptEnum.DELETE.getCode(), param.getId()));
        return removed;
    }

    private void checkFlows(Integer strategyId, Integer decisionId, List<Flow> flows) {
        if (CollectionUtils.isEmpty(flows)) {
            return;
        }
        for (Flow flow : flows) {
            if (Objects.equals(flow.getNodeType(), NodeTypeEnum.DECISION.getCode())
                    && Objects.equals(flow.getNodeId(), decisionId)) {
                throw new RuntimeException(String.format("该决策已经被策略%s引用，请先解除引用!", strategyId));
            }
            checkFlows(strategyId, decisionId, flow.getChildren());
        }
    }

    public boolean checkDeleteFeature(Integer featureId) {
        for (Decision decision : list()) {
            Set<Integer> noticeFeatureIds = TextUtil.getVariables(decision.getNoticeMsg());
            Set<Integer> resultFeatureIds = TextUtil.getVariables(decision.getResultMsg());
            if (noticeFeatureIds.contains(featureId) || resultFeatureIds.contains(featureId)) {
                throw new RuntimeException(String.format("该特征已经被决策%s引用，请先解除引用!", decision.getId()));
            }
        }
        return true;
    }
}
